package com.hxht.storage.server.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.quartz.DateBuilder;
import org.quartz.DateBuilder.IntervalUnit;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.core.jmx.JobDataMapSupport;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.hxht.storage.server.bean.ManualRecordBean;
import com.hxht.storage.server.bean.NvrRegisteBean;
import com.hxht.storage.server.bean.NvrUpdateBean;
import com.hxht.storage.server.bean.PlanRecordParamBean;
import com.hxht.storage.server.dao.NvrDeviceDao;
import com.hxht.storage.server.dao.NvrDiskDao;
import com.hxht.storage.server.dao.NvrIpchannelDao;
import com.hxht.storage.server.dao.PlanRecordDao;
import com.hxht.storage.server.dao.WeekTemplateDao;
import com.fxdigital.nvr.DeviceInfo;
import com.fxdigital.nvr.DiskInfo;
import com.fxdigital.nvr.FindCondition;
import com.fxdigital.nvr.FindResult;
import com.fxdigital.nvr.IPChannelInfo;
import com.fxdigital.nvr.NVRCtrl;
import com.fxdigital.nvr.PlanRecordParam;
import com.fxdigital.nvr.TimeInfo;
import com.hxht.storage.server.pojo.DayTemplateInfo;
import com.hxht.storage.server.pojo.NvrDeviceInfo;
import com.hxht.storage.server.pojo.NvrDiskInfo;
import com.hxht.storage.server.pojo.NvrIpchannelInfo;
import com.hxht.storage.server.pojo.PlanRecordInfo;
import com.hxht.storage.server.pojo.VideoDeviceChannelInfo;
import com.hxht.storage.server.pojo.VideoManualRecord;
import com.hxht.storage.server.pojo.WeekTemplateInfo;
import com.hxht.storage.server.service.ManualRecordService;
import com.hxht.storage.server.service.NvrDeviceService;
import com.hxht.storage.server.service.PlanRecordService;
import com.hxht.storage.server.service.SchedulerService;
import com.hxht.storage.server.service.quartz.job.IpchannelInitScanJob;
import com.hxht.storage.server.service.quartz.job.IpchannelUnbindJob;
import com.hxht.storage.server.service.quartz.job.ManualRecordStopJob;
import com.hxht.storage.server.service.quartz.job.NvrScanJob;
import com.hxht.storage.server.service.quartz.job.PlanRecordStartJob;
import com.hxht.storage.server.util.NvrBeanTransUtil;

@Service("nvrDeviceService")
public class NvrDeviceServiceImpl implements NvrDeviceService
							, SchedulerService{
	
	private static final Logger LOG = Logger.getLogger(NvrDeviceServiceImpl.class);

	private NVRCtrl dvrCtrlInstance;
	
	@Autowired
	SchedulerFactoryBean schedulerFacotry;
	
	@Autowired
	private NvrIpchannelDao ipchannelDao;
	
	@Autowired
	private NvrDeviceDao dvrDeviceDao;
	
	@Autowired
	private NvrDiskDao nvrDiskDao;
	
	@Autowired
	private ManualRecordService manualRecordService;
	
	@Autowired
	private PlanRecordService planRecordService;
	
	@Autowired
	private WeekTemplateDao weekTemplateDao;
	
	@Autowired
	private PlanRecordDao planRecordDao;
	
	private Map<String, Integer> dvrSessions = new HashMap<String, Integer>();
	
	@Value(value="${quartz.schedule.interval}")
	private int SCHEDULE_INTERVER;
	
	@Value(value="${quartz.schedule.maxRepeat}")
	private int MAX_REPEAT;
	
	/**
	 * 注册NVR设备。<br/>
	 * 只有当 和 该nvr建立会话句柄，并且获得该nvr SN号、最大通道数才算注册成功。
	 * @return 注册成功与否
	 */
	@Override
	@Transactional(propagation=Propagation.REQUIRED, value="nvmpJpaEM")
	public boolean registerNvrDevice(NvrRegisteBean dvrDevice) {
		
		LOG.info("register nvr is beginning---------------");
		LOG.info("---nvrIp 	  : " + dvrDevice.getIp() + "---");
		LOG.info("---port 	  : " + dvrDevice.getPort() + "---");
		
		NvrDeviceInfo nvr = transNvrBeanToNvr(dvrDevice);
		String ip = nvr.getIp();
		int port = nvr.getPort();
		String userName =nvr.getUsername();
		String password = nvr.getPasswd();
		int type = nvr.getDeviceType();
		int subType = nvr.getSubDeviceType();
		
		// 判断该IP地址的nvr是否已存在，存在着返回 注册成功
		if (dvrDeviceDao.isNvrRegisted(ip)){
			LOG.warn("nvr has been registed!");
			return true;
		}
		
		int session = dvrCtrlInstance.NVRCreateSession(type, 
				subType, ip, port, userName, password);
		if (session == -1){
			LOG.warn("nvr is not on line!");
			// 表示nvr建立会话失败，不在线，注册失败
			nvr.setIsOnline(false);
			return false;
		} else {
			nvr.setIsOnline(true);
			
			// 获取该注册nvr的SN号 和 支持的最大IP通道数
			DeviceInfo deviceInfo = new DeviceInfo();
			int devInfoResult = dvrCtrlInstance.NVRGetDeviceInfo(session, deviceInfo);
			if (devInfoResult == 1){
				// 将nvr的SN号，保存
				nvr.setDeviceId(deviceInfo.getSerialNumber());
				nvr.setMaxIpchannels(deviceInfo.getMaxIPChannel());

				dvrSessions.put(nvr.getIp(), session);
				dvrDeviceDao.insert(nvr);
				
				// 初始化nvr的所有ip通道
				boolean ipchannelInitResult = initScanIpchannel(deviceInfo.getSerialNumber());
//				boolean ipchannelInitResult = false;
				// 如果初始化ip通道失败 则启动quartz定时任务，去初始化该nvr的ip通道
				if (!ipchannelInitResult){
					LOG.warn("init nvr's ipchannels fail!");
					scheduleInitScanIpchannelJob(nvr.getIp());
				}
				
			} else {
				LOG.warn("call nvr.dll to get nvr's deviceInfo fail!");
				// 获取nvr会话句柄成功，但是获得nvr信息失败时，
				return false;
			}
		}
		
		// 注册成功后，需要开启定时任务，去扫描该nvr是否在线
		scheduleScanNvrJob(nvr.getDeviceId());

		LOG.info("register nvr is successed!--------------");
//		dvrDeviceDao.insert(nvr);
		return true;
	}
	
	/**
	 * 扫描nvr当前的 ip通道，并全部解除绑定
	 * @param nvr
	 * @return 表示获得nvr ip通道信息成功，并解除了所有ip通道。<br/>
	 * 		   对于当前解除绑定失败的通道，会启动定时任务去解除绑定。
	 */
	@Transactional(propagation=Propagation.REQUIRED, value="nvmpJpaEM")
	public boolean initScanIpchannel(String sn) {

		NvrDeviceInfo nvr = dvrDeviceDao.getNvrBySN(sn);
		String nvrIp = nvr.getIp();
		
		LOG.info("ipchannel init scan is beginning---------------");
		LOG.info("---nvrIp 	  : " + nvrIp + "---");

		int session = dvrSessions.get(nvr.getIp());
		if (session == -1) {
			LOG.warn("nvr is not online!");
			// 表示nvr建立会话失败，不在线
			return false;
		}

		NvrIpchannelInfo[] newIpchannelList = new NvrIpchannelInfo[nvr
				.getMaxIpchannels()];
		List<IPChannelInfo> infoList = new ArrayList<IPChannelInfo>();
		int ipchannelCtrResult = dvrCtrlInstance.NVRIPChannelCtrl(session, 3,
				infoList, 0);
		if (ipchannelCtrResult != 0) {

			List<NvrIpchannelInfo> existsIpchannelList = ipchannelDao
					.findByNvr(nvrIp);
			// 若该nvr的 ip通道信息已存在数据库中，则更新其状态为ilde，并向nvr发送解除绑定通道的命令
			if (existsIpchannelList != null && existsIpchannelList.size() != 0) {
				for (NvrIpchannelInfo ipchannel:existsIpchannelList){
					// 初始化ip通道时，若该通道已被绑定，则解除绑定
					if (!ipchannel.isIdle()){
						
						// 将该ip通道正在录像的记录进行停止设置，更新记录
						manualRecordService.stopRecordingOfIpchannel(nvrIp, ipchannel.getNvrChannel());
						planRecordService.stopRecordingOfIpchannel(nvrIp, ipchannel.getNvrChannel());
						// 将该 nvr当前正在使用的 ip通道解除绑定
						int result = dvrCtrlInstance.NVRIPChannelCtrl(session, 2,
								null, ipchannel.getNvrChannel());
						if (result == 0) {

							LOG.warn("call nvr.dll to unbind ipchannel fail!");
							// 需要启动 解除该ip通道的定时任务
							scheduleUnbindIpchannelJob(nvr.getDeviceId(),
									ipchannel.getNvrChannel());
						} else {
							ipchannel.setIsIdle(true);
						}
					} else {
						// 数据库中记录的该ip通道为空闲，但是NVR中显示该通道已被绑定
						// 则 解除绑定
						// 将获得的该nvr的 ip通道信息 和 相应的nvr信息进行关联
						for (IPChannelInfo info : infoList) {
							
							if (ipchannel.getNvrChannel() != info.getNvrChannel())
								continue;
							
							LOG.warn("数据库中的ipchannel信息 和 nvr实际 不一致！nvrIp = " 
										+ nvr.getIp() + " / ipchannel = " + info.getNvrChannel());
							// 将该 nvr当前正在使用的 ip通道解除绑定
							int result = dvrCtrlInstance.NVRIPChannelCtrl(session, 2,
									null, info.getNvrChannel());
							if (result == 0) {
								ipchannel.setIsIdle(false);

								LOG.warn("call nvr.dll to unbind ipchannel fail!");
								// To-Do
								// 需要启动 解除该ip通道的定时任务
								scheduleUnbindIpchannelJob(nvr.getDeviceId(),
										info.getNvrChannel());
							} else {
								LOG.warn("已修正ipchannel信息 不一致！nvrIp = " 
										+ nvr.getIp() + " / ipchannel = " + info.getNvrChannel());
							}
						}
					}
				}
			} else {

				// 将获得的该nvr的 ip通道信息 和 相应的nvr信息进行关联
				for (IPChannelInfo info : infoList) {
					NvrIpchannelInfo ipchannel = transNvrIpchannelToDBIpchannel(info);
					ipchannel.setNvrDevice(nvr);

					// 登记时，将该 nvr当前正在使用的 ip通道解除绑定
					int result = dvrCtrlInstance.NVRIPChannelCtrl(session, 2,
							null, info.getNvrChannel());
					if (result == 0) {
						ipchannel.setIsIdle(false);

						LOG.warn("call nvr.dll to unbind ipchannel fail!");
						// To-Do
						// 需要启动 解除该ip通道的定时任务
						scheduleUnbindIpchannelJob(nvr.getDeviceId(),
								info.getNvrChannel());
					} else {
						ipchannel.setIsIdle(true);
					}

					// nvr的 ip通道号 从1开始计数
					newIpchannelList[info.getNvrChannel() - 1] = ipchannel;
				}

				// 生成该nvr的空余ip通道，并设置关联关系
				for (int i = 0; i < newIpchannelList.length; i++) {
					if (newIpchannelList[i] == null) {
						newIpchannelList[i] = new NvrIpchannelInfo();
						newIpchannelList[i].setDvrChannel(i + 1);
						newIpchannelList[i].setIsIdle(true);
						newIpchannelList[i].setNvrDevice(nvr);
					}
				}

				// 将该nvr的 ip通道信息写入数据库
				for (NvrIpchannelInfo ipchannel : newIpchannelList) {
					ipchannelDao.insert(ipchannel);
				}
			}
		} else {
			LOG.warn("call nvr.dll to get nvr's ipchannels info fail!");
			return false;
		}

		LOG.info("ipchannel init scan is successed---------------");

		return true;
	}

	/**
	 * 将通过nvr.dll jni调用返回的 IPChannelInfo ip通道信息，转化为数据库中的NvrIpchannelInfo信息
	 * @param info
	 * @return
	 */
	private NvrIpchannelInfo transNvrIpchannelToDBIpchannel(IPChannelInfo info) {
		NvrIpchannelInfo ipchannel = new NvrIpchannelInfo();
	
		ipchannel.setDvrChannel(info.getNvrChannel());
		ipchannel.setIsOnline(info.getIsOnline() == 1 ? true:false);
		ipchannel.setProtocol(info.getProtocol());
		ipchannel.setStreamMode(info.getStreamMode());
		ipchannel.setIp(info.getIp());
		ipchannel.setPort(info.getPort());
		ipchannel.setUserName(info.getUserName());
		ipchannel.setPassword(info.getPassword());
		ipchannel.setDeviceChannel(info.getChannel());
		
		return ipchannel;
	}

	/**
	 * 将用户注册nvr的信息 NvrRegisteBean 转化为 数据库中的NvrDeviceInfo信息
	 * @param dvrBean
	 * @return
	 */
	private NvrDeviceInfo transNvrBeanToNvr(NvrRegisteBean dvrBean) {
		// TODO Auto-generated method stub
		NvrDeviceInfo dvr = new NvrDeviceInfo();
		dvr.setIp(dvrBean.getIp());
		dvr.setUsername(dvrBean.getUsername());
		dvr.setPasswd(dvrBean.getPasswd());
		dvr.setDeviceType(dvrBean.getDeviceType());
		dvr.setPort(dvrBean.getPort());
		dvr.setSubDeviceType(dvrBean.getSubDeviceType());
		
		return dvr;
	}

	@Override
	public List<NvrIpchannelInfo> getAllNvrIpchannel() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<NvrIpchannelInfo> getIdleNvrIpchannel() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public NvrIpchannelInfo selectOneIpchannelToRecord(
			ManualRecordBean recordBean) {
		// TODO Auto-generated method stub
		
		List<NvrIpchannelInfo> ipchannelList = ipchannelDao.findIdleIpchannelList();
		NvrIpchannelInfo ipchannel = null;
		if (ipchannelList != null && ipchannelList.size() != 0){
			ipchannel = ipchannelList.get(0);
		}
		
		
		return ipchannel;
	}
	
	@PreDestroy
	public void close(){
		LOG.info("nvr uninit");
		dvrCtrlInstance.NVRUnInit();
	}

//	@Transactional(value="nvmpJpaEM", propagation=Propagation.REQUIRED)
//	@PostConstruct
	@Override
	public void init() {
		
		LOG.info("nvrDeviceService init is beginning---------------");
		
		// 初始化 DVRCtrl, 以操作nvr.dll
		dvrCtrlInstance = new NVRCtrl();
		dvrCtrlInstance.NVRInit();
		
		// 初始化quartz scheduler
		resumeQuartzScheduler();
		
		List<NvrDeviceInfo> dvrList = dvrDeviceDao.findAll();
		
		for (NvrDeviceInfo nvr : dvrList){
			String ip = nvr.getIp();
			int port = nvr.getPort();
			String userName =nvr.getUsername();
			String password = nvr.getPasswd();
			int type = nvr.getDeviceType();
			int subType = nvr.getSubDeviceType();
			int session = dvrCtrlInstance.NVRCreateSession(type, 
					subType, ip, port, userName, password);
//			int session = -1;
			if (session == -1){
				LOG.warn("nvr is not online! nvrIp = " + ip);
				// 表示nvr建立会话失败，不在线
				nvr.setIsOnline(false);
			} else {
				nvr.setIsOnline(true);
			}
			
			dvrSessions.put(nvr.getIp(), session);
			dvrDeviceDao.update(nvr);
			
			// 增加定时任务，定时去检测 NVR是否在线
			scheduleScanNvrJob(nvr.getDeviceId());
		}
		
		LOG.info("nvrDeviceService init is end---------------");
	}
	
	/**
	 * 判断当前quartz正在运行的triggers中，是否包含TriggerKey key
	 * @return
	 */
	private boolean isTriggerExists(TriggerKey key){
		Scheduler scheduler = schedulerFacotry.getScheduler();
		
		Set<TriggerKey> triggers = null;
		try {
			triggers = scheduler.getTriggerKeys(GroupMatcher
					.anyTriggerGroup());
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			LOG.error("get scheduler triggers fail!", e);
		}
		
		if (triggers != null && triggers.contains(key))
			return true;
		
		return false;
	}

	@Override
	public void resumeQuartzScheduler() {
		
		LOG.info("resume quartz scheduler is beginning---------------");
		
		try {
			Scheduler scheduler = schedulerFacotry.getScheduler();
			// ①获取调度器中所有的触发器组

			Set<TriggerKey> triggers = scheduler.getTriggerKeys(GroupMatcher
					.anyTriggerGroup());
			Iterator<TriggerKey> triggerIterator = triggers.iterator();

			while (triggerIterator.hasNext()) {
				TriggerKey key = triggerIterator.next();
				Trigger trigger = scheduler.getTrigger(key);

				// ②-1:恢复运行
				scheduler.rescheduleJob(key, trigger);
			}

			scheduler.start();
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			LOG.error("init quartz scheduler fail!", e);
		}
		
		LOG.info("resume quartz scheduler is end---------------");
	}

	@Override
	public VideoManualRecord startManualRecord(ManualRecordBean recordBean) {
		
		LOG.info("start manualRecord is beginning---------------");
		LOG.info("---deviceId 	  : " + recordBean.getDeviceId() + "---");
		LOG.info("---channelIndex : " + recordBean.getDeviceChannelIndex() + "---");
		
		VideoManualRecord record = transRecordBeanToDbRecord(recordBean);
		
		// record == null 
		// 说明 没有找到可用的ip通道
		if (record == null){
			LOG.warn("no idle ipchannel!");
			return null;
		}
		
		String ip = record.getIpChannel().getNvrDevice().getIp();
		Integer session = dvrSessions.get(ip);
		// 1 - 海康设备	0 - 保留子类型
//		int session = dvrCtrlInstance.DVRCreateSession(1, 0, ip, 8000, "admin", "12345");
		if (session == -1){
			LOG.warn("nvr is not online!");
			return null;
		}

		List<IPChannelInfo> listIPChannelInfo = new ArrayList<IPChannelInfo>();
		// IPChannelInfo 是 和 nvr.dll jni调用时的接口参数
		IPChannelInfo ipchannel = transRecordChannelToNvrIpchannel(record.getVideoDeviceChannel());
		listIPChannelInfo.add(ipchannel);

		// ctrlCode 1 - 添加ipchannel绑定
		int ipchannelBindResult = dvrCtrlInstance.NVRIPChannelCtrl(session,
					1, listIPChannelInfo, ipchannel.getNvrChannel());
		if (ipchannelBindResult != 1){
			LOG.warn("call nvr.dll to bind ipchannel fail!");
			return null;
		}
		// 根据返回的被绑定ip通道号，找到数据库中对应的DvrIpchannelInfo,并设置参数
		NvrIpchannelInfo dbIpchannel = ipchannelDao.findByNvrChannel(ip, ipchannel.getNvrChannel());
		record.getIpChannel().setDvrChannel(ipchannel.getNvrChannel());
		NvrBeanTransUtil.cloneIpchannelInfo(record.getIpChannel(), dbIpchannel);
		record.setIpChannel(dbIpchannel);
		
		// 绑定ip通道后，需要先检测ip通道录像设备，是否在线
		// 只有在线状态，才能开始录像
		int count = 10;
		while (count > 0){
			
			if (isOnline(ip, ipchannel.getNvrChannel()))
				break;
		
			// 若当前不在线，则休眠100ms,再去检测
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				LOG.warn("ipchannel online check is interruptted!");
				LOG.warn("nvrIp = " + ip + " / ipchannel = " + ipchannel.getNvrChannel());
			}
			count --;
		}
		
		if (count == 0){
			LOG.warn("ipchannel is not online!");
		}
		
		PlanRecordParam param = new PlanRecordParam();
		int recordResult = dvrCtrlInstance.NVRRecordCtrl(session, 2, 1, ipchannel.getNvrChannel(), 0, record.getManualRecordType(), param);
		if (recordResult != 1){
			LOG.warn("call nvr.dll to start manual record fail!");
			// 手工录像失败后， 需要开启定时任务去释放 被绑定的ip通道
			scheduleUnbindIpchannelJob(record.getIpChannel().getNvrDevice().getDeviceId(), ipchannel.getNvrChannel());
			return null;
		}
		
		// 手工录像后，立刻获得nvr系统时间，并记为录像开始时间
		TimeInfo timeInfo = new TimeInfo();
		int curTime = dvrCtrlInstance.NVRGetCurrentTime(session, timeInfo);
		if (curTime == 1){
			Date startDate = NvrBeanTransUtil.transTimeInfoToDate(timeInfo);
			record.setStartTime(startDate);
		}else{
			// 获取NVR当前时间失败时，使用本地时间
			record.setStartTime(new Date());
		}

		LOG.info("start manualRecord success!---------------");
		LOG.info("record at nvr = " + record.getIpChannel().getNvrDevice().getIp() +
				" / ipchannel = " + record.getIpChannel().getNvrChannel());
		
		return record;
		
	}
	
	
	/**
	 * 通过nvr.dll去获取nvr 当前ip通道在线状态，从而判断nvrChannel所指向的ip通道当前是否在线
	 * @param ip
	 * @param nvrChannel
	 * @return
	 */
	private boolean isOnline(String ip, int nvrChannel) {

		int session = dvrSessions.get(ip);
		
		List<IPChannelInfo> listIPChannelInfo = new ArrayList<IPChannelInfo>();
		
		// ctrlCode = 3,表示 查询ip通道状态
		int checkResult = dvrCtrlInstance.NVRIPChannelCtrl(session, 3, listIPChannelInfo, 0);
		if (checkResult == 0){
			LOG.warn("call nvr.dll to get nvr's ipchannel online status fail!");
			LOG.info("record at nvr = " + ip + " / ipchannel = " + nvrChannel);
		}
		
		for (IPChannelInfo ipchannel:listIPChannelInfo){
			if (ipchannel.getNvrChannel() == nvrChannel)
				// isOnline = 1, 表示 在线
				return ipchannel.getIsOnline() == 1;
		}
		
		return false;
	}

	@Override
	public boolean stopManualRecord(VideoManualRecord recording, boolean unbindIpchannel) {
		
		LOG.info("stop manualRecord is beginning---------------");
		LOG.info("---deviceId 	  : " + recording.getDeviceId() + "---");
		LOG.info("---channelIndex : " + recording.getDeviceChannel() + "---");
		
		NvrDeviceInfo nvr = recording.getIpChannel().getNvrDevice();
		String nvrIp = nvr.getIp();
		Integer session = dvrSessions.get(nvrIp);
		if (session == -1){
			return false;
		}
		
		TimeInfo timeInfo = new TimeInfo();
		int curTime = dvrCtrlInstance.NVRGetCurrentTime(session, timeInfo);
		
		int ipchannelIndex = recording.getIpChannel().getNvrChannel();
		// recordMode-2 手工录像
		// ctrlCode-2 停止录像
		int stopResult = dvrCtrlInstance.NVRRecordCtrl(session, 2, 2, ipchannelIndex, 0, 0, null);
		if (stopResult == 0){
			LOG.warn("call nvr.dll to stop manual record fail!");
			return false;
		}

		if (curTime == 1){
			Date stopDate =NvrBeanTransUtil.transTimeInfoToDate(timeInfo);
			recording.setStopTime(stopDate);
		}else{
			// 获取NVR当前时间失败时，使用本地时间
			recording.setStopTime(new Date());
		}
		recording.setIsRecoding(false);
		
		if (unbindIpchannel){
			// 解除被绑定的NVR ip通道
			// ctrlCode-2 删除绑定的nvr ip通道
			int ipchannelBindResult = dvrCtrlInstance.NVRIPChannelCtrl(session, 2, null, recording.getIpChannel().getNvrChannel());
			if (ipchannelBindResult == 1){
				recording.getIpChannel().setIsIdle(true);
			} else {
				LOG.warn("call nvr.dll to unbind ipchannel fail!");
				recording.getIpChannel().setIsIdle(false);
				// To-Do 
				// 启动定时任务去解除绑定
				scheduleUnbindIpchannelJob(recording.getIpChannel().getNvrDevice().getDeviceId(), ipchannelIndex);
			}
		}
		
		// 停止录像时，需要取消定时关闭手工录像的任务
		try {
			// 删除老的停止录像任务
			String triggerName = nvr.getDeviceId() + ":" 
					+ recording.getIpChannel().getNvrChannel() + ":stop";
			String triggerGroup = nvr.getDeviceId();
			Trigger existTrigger =schedulerFacotry.getScheduler().
					getTrigger(TriggerKey.triggerKey(triggerName, triggerGroup));
			if (existTrigger != null){
				schedulerFacotry.getScheduler().pauseTrigger(existTrigger.getKey());     
				schedulerFacotry.getScheduler().unscheduleJob(existTrigger.getKey()); 
			}
			
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			LOG.warn("unscheduler stop manual record job fail!", e);
		};     
		
		LOG.info("stop manualRecord success!---------------");
		
		return true;
	}
	
	private VideoManualRecord transRecordBeanToDbRecord(
			ManualRecordBean recordBean) {
		// TODO Auto-generated method stub
		VideoManualRecord record = new VideoManualRecord();

		VideoDeviceChannelInfo deviceChannelInfo = getDeviceChannelInfoFromRecordBean(recordBean);
		
		// 没有可用的ip通道，返回null
		String nvrIp = hasIdleIpchannel();
		if (nvrIp == null) {
			return null;
		}
		NvrIpchannelInfo ipchannel = new NvrIpchannelInfo();
		ipchannel.setNvrDevice(dvrDeviceDao.getNvrByIp(nvrIp));
		record.setIpChannel(ipchannel);
		IPChannelInfo info = transRecordChannelToNvrIpchannel(deviceChannelInfo);
		ipchannel.setProtocol(info.getProtocol());
		ipchannel.setStreamMode(info.getStreamMode());
		ipchannel.setIp(info.getIp());
		ipchannel.setPort(info.getPort());
		ipchannel.setUserName(info.getUserName());
		ipchannel.setPassword(info.getPassword());
		ipchannel.setDeviceChannel(info.getChannel());
		//设置该通道标示，正在被使用
		ipchannel.setIsIdle(false);
		
		record.setVideoDeviceChannel(deviceChannelInfo);
		
		record.setDeviceId(recordBean.getDeviceId());
		
		record.setDeviceChannel(recordBean.getDeviceChannelIndex());
		
		record.setIsRecoding(true);
		
		record.setManualRecordType(recordBean.getRecordType());
		
		record.setMark(recordBean.getMark());
		
		record.setUserId(recordBean.getUserId());
		
		return record;
	}
	
	@Override
	/** 
	 * 
	 * @return 若找到可用的ip通道，则返回该通道所对应的nvr ip地址
	 */
	public String hasIdleIpchannel() {
		// TODO Auto-generated method stub
		List<NvrIpchannelInfo> ipchannelList = ipchannelDao.findIdleIpchannelList();
		if (ipchannelList != null && ipchannelList.size() != 0){
			return ipchannelList.get(0).getNvrDevice().getIp();
		} else {
			return null;
		}
	}

	private VideoDeviceChannelInfo getDeviceChannelInfoFromRecordBean(
			ManualRecordBean recordBean) {
		// TODO Auto-generated method stub
		VideoDeviceChannelInfo deviceChannelInfo = new VideoDeviceChannelInfo();
		
		// 业务知识：当 deviceChannelIndex < 1000 时，表示使用的是 主码流
		//       当 deviceChannelIndex >= 1000 时，表示使用的是 子码流
		if (recordBean.getDeviceChannelIndex() < 1000){
			deviceChannelInfo.setTransMode(0);
		} else {
			deviceChannelInfo.setTransMode(1);
		}
		
		// 当device typ 为250时，表示标准流
		if (recordBean.getDeviceType() == 250){
			deviceChannelInfo.setStreamMode(2);
		} else { 
			deviceChannelInfo.setStreamMode(1);
		}
		
		deviceChannelInfo.setProtocol(recordBean.getNetLinkMode());
		
		deviceChannelInfo.setStreamUrl(recordBean.getVideoServerIp());
		
		deviceChannelInfo.setVideoServerChannelIndex(recordBean.getVideoServerChannel());
		
		deviceChannelInfo.setUsername(recordBean.getUsername());
		
		deviceChannelInfo.setPasswd(recordBean.getPasswd());
		
		deviceChannelInfo.setPort(recordBean.getPort());
		
		return deviceChannelInfo;
	}

	
	private IPChannelInfo transRecordChannelToNvrIpchannel(VideoDeviceChannelInfo videoDeviceChannelInfo) {
		// TODO Auto-generated method stub
		IPChannelInfo ipchannel = new IPChannelInfo();
		
		ipchannel.setChannel(videoDeviceChannelInfo.getVideoServerChannelIndex());
		
		ipchannel.setIp(videoDeviceChannelInfo.getStreamUrl());
		if (videoDeviceChannelInfo.getStreamMode() == 1){
			// 海康私有流
			ipchannel.setProtocol(0);
		} else {
//			ipchannel.setIp(getIpFromRtspUrl(videoDeviceChannelInfo.getStreamUrl()));
			// 18,表示 ONVIF 标准流协议
			ipchannel.setProtocol(18);
//			ipchannel.setStreamURL(videoDeviceChannelInfo.getStreamUrl());
		}
		
		ipchannel.setPassword(videoDeviceChannelInfo.getPasswd());
		ipchannel.setPort(videoDeviceChannelInfo.getPort());
		
//		ipchannel.setStreamMode(videoDeviceChannelInfo.getStreamMode());
		ipchannel.setStreamMode(1);
		ipchannel.setUserName(videoDeviceChannelInfo.getUsername());
		
		return ipchannel;
	}

	/**
	 * 从 rtsp标准流地址中提取 ip地址的方法
	 * @param streamUrl 格式如   rtsp://admin:12345@192.168.1.121:554/2
	 * @return
	 */
	private String getIpFromRtspUrl(String streamUrl) {
		// TODO Auto-generated method stub
//		Pattern pattern = Pattern.compile("://([\\d.]+)[:/]");
		Pattern pattern = Pattern.compile("://([\\w_]+:[\\w_]+@)?([\\d.]+)[:/]");
		Matcher matcher = pattern.matcher(streamUrl);
		matcher.find();
		String ip = matcher.group(2);
		return ip;
	}

	@Override
	public boolean removeTrigger(TriggerKey key) throws SchedulerException {
		schedulerFacotry.getScheduler().pauseTrigger(key);     
	     return schedulerFacotry.getScheduler().unscheduleJob(key);  
	}

	
	@Override
	public void scheduleInitScanIpchannelJob(String sn) {

		// 获得nvr信息成功，但是获取ip通道绑定信息失败时，
		// 需要启动 获取并初始化该nvr ip通道的定时任务
		SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder
				.simpleSchedule().withIntervalInMinutes(SCHEDULE_INTERVER).withRepeatCount(MAX_REPEAT);//.repeatForever();

		// job名 示例 192.168.1.44:ipchannelScan
		// job组名 为 nvrIp,例如 192.168.1.144
		JobDetail jobDetail = JobBuilder.newJob(IpchannelInitScanJob.class)
					.requestRecovery(true).withIdentity(sn + ":ipchannelScan", sn)
					.usingJobData("sn", sn).build();
		
		Trigger trigger = TriggerBuilder.newTrigger()
				.withIdentity(sn + ":ipchannelScan", sn)
				.forJob(jobDetail).startAt(DateBuilder.futureDate(SCHEDULE_INTERVER, IntervalUnit.MINUTE))
				.withSchedule(schedBuilder).build();
		try {
			// 删除老的停止录像任务
			Trigger existTrigger =schedulerFacotry.getScheduler().getTrigger(trigger.getKey());
			if (existTrigger != null){
				schedulerFacotry.getScheduler().pauseTrigger(existTrigger.getKey());     
				schedulerFacotry.getScheduler().unscheduleJob(existTrigger.getKey()); 
			}
			
			// 若有重复的job, 覆盖该job
			schedulerFacotry.getScheduler().addJob(jobDetail, true, true);
			schedulerFacotry.getScheduler().scheduleJob(trigger);
			
			LOG.info("schedule nvr's ipchannel init scan job! nvrIp = " + sn);
		} catch (SchedulerException e) {
			LOG.error("nvr ipchannel init job fail!", e);
		}
		
	}

	@Override
	public void scheduleUnbindIpchannelJob(String sn, int channel) {

		// 获得nvr信息成功，但是获取ip通道绑定信息失败时，
		// 需要启动 获取并初始化该nvr ip通道的定时任务
		SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder
				.simpleSchedule().withIntervalInMinutes(SCHEDULE_INTERVER).withRepeatCount(MAX_REPEAT);//.repeatForever();

		// job名例如  XT1620140829BBRR478206093WCVU:2，
		// job组名 为 nvr sn, 例如 XT1620140829BBRR478206093WCVU
		JobDetail jobDetail = JobBuilder.newJob(IpchannelUnbindJob.class)
					.requestRecovery(true).withIdentity(sn + ":" + channel,sn)
					.usingJobData("sn", sn).usingJobData("channel", channel).build();
		
		Trigger trigger = TriggerBuilder.newTrigger()
				.withIdentity(sn + ":" + channel,sn).forJob(jobDetail)
				.startNow().withSchedule(schedBuilder).build();
		try {
			// 删除老的停止录像任务
			Trigger existTrigger =schedulerFacotry.getScheduler().getTrigger(trigger.getKey());
			if (existTrigger != null){
				schedulerFacotry.getScheduler().pauseTrigger(existTrigger.getKey());     
				schedulerFacotry.getScheduler().unscheduleJob(existTrigger.getKey()); 
			}
			
			// 若有重复的job, 覆盖该job
			schedulerFacotry.getScheduler().addJob(jobDetail, true, true);
			schedulerFacotry.getScheduler().scheduleJob(trigger);
			
			LOG.info("schedule ipchannel unbind job! nvrIp = " +
					sn + " / channel = " + channel);
		} catch (SchedulerException e) {
			LOG.error("schedule ipchannel unbind job fail!", e);
		}

	}

	@Override
	public void scheduleStopRecordJob(String sn, int channel, boolean isUnbindIpchannel, int recordDuration) {

		// 获得nvr信息成功，但是获取ip通道绑定信息失败时，
		// 需要启动 获取并初始化该nvr ip通道的定时任务
		SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder
				.simpleSchedule().withIntervalInMinutes(SCHEDULE_INTERVER).withRepeatCount(MAX_REPEAT);//.repeatForever();

		// job名例如  XT1620140829BBRR478206093WCVU:2:stop，
		// job组名 为 nvr sn, 例如 XT1620140829BBRR478206093WCVU
		JobDetail jobDetail = JobBuilder.newJob(ManualRecordStopJob.class)
					.requestRecovery(true).withIdentity(sn + ":" + channel + ":stop",sn)
					.usingJobData("sn", sn).usingJobData("channel", channel)
					.usingJobData("isUnbindIpchannel", isUnbindIpchannel).build();
		
		Trigger trigger = TriggerBuilder.newTrigger()
				.withIdentity(sn + ":" + channel + ":stop",sn).forJob(jobDetail)
				.startAt(DateBuilder.futureDate(recordDuration, IntervalUnit.SECOND))
				.withSchedule(schedBuilder).build();
		try {
			// 删除老的停止录像任务
			Trigger existTrigger =schedulerFacotry.getScheduler().getTrigger(trigger.getKey());
			if (existTrigger != null){
				schedulerFacotry.getScheduler().pauseTrigger(existTrigger.getKey());     
				schedulerFacotry.getScheduler().unscheduleJob(existTrigger.getKey()); 
			}
			
			// 若有重复的job, 覆盖该job
			schedulerFacotry.getScheduler().addJob(jobDetail, true, true);
			schedulerFacotry.getScheduler().scheduleJob(trigger);
			
			LOG.info("schedule stop record job! nvrIp = " +
					sn + " / channel = " + channel);
		} catch (SchedulerException e) {
			LOG.error("schedule stop record job fail!", e);
		}
	}

	@Override
	@Transactional(value="nvmpJpaEM", propagation=Propagation.REQUIRED)
	public boolean unbindIpchannel(String sn, int channel) {
		
		NvrDeviceInfo nvr = dvrDeviceDao.getNvrBySN(sn);
		String nvrIp = nvr.getIp();
		
		LOG.info("unbind ipchannel is beginning---------------");
		LOG.info("---nvrIp 	  : " + nvrIp + "---");
		LOG.info("---channel  : " + channel + "---");
		
		// nvr 不在线，返回失败
		if (!nvr.isOnline()){
			return false;
		}

		int session = dvrSessions.get(nvrIp);
		
		NvrIpchannelInfo ipchannel = ipchannelDao.findByNvrChannel(nvrIp, channel);
		// ip通道 已经解除绑定，直接返回成功
		if (ipchannel.isIdle()){
			return true;
		}

		// 将该 nvr当前正在使用的 ip通道解除绑定
		int result = dvrCtrlInstance.NVRIPChannelCtrl(session, 
							2, null, ipchannel.getNvrChannel());
		if (result == 0) {
			LOG.warn("call nvr.dll to unbind ipchannel fail!");
			ipchannel.setIsIdle(false);
		} else {
			ipchannel.setIsIdle(true);
		}

		ipchannelDao.update(ipchannel);

		// nvr.dll jni调用 ip通道解除绑定，返回结果为非0，则代表解除绑定成功
		return (result != 0);
	}

	@Override
	@Transactional(value="nvmpJpaEM", propagation=Propagation.REQUIRED)
	public List<NvrDeviceInfo> getAllNvr() {
		List<NvrDeviceInfo> nvrList = dvrDeviceDao.findAll();
		
		for (NvrDeviceInfo nvr:nvrList){
			// nvr处于在线状态，才去获得磁盘信息
			if (nvr.isOnline()){
				// 获取NVR的磁盘空间状态
				int session = dvrSessions.get(nvr.getIp());
				List<DiskInfo> diskInfoList = new ArrayList<DiskInfo>();
				
				int result = dvrCtrlInstance.NVRDiskCtrl(session, 0, 2, diskInfoList);
				// result == 0, 表示获取信息失败
				if (result == 0){
					LOG.warn("call nvr.dll to get nvr's disk info fail! nvrIp = " + nvr.getIp());
					continue;
				}
				
				nvr.getDiskList().clear();
				for (int i = 0; i < diskInfoList.size(); i++){
					DiskInfo disk = diskInfoList.get(i);
					
					// nvr disk的编号 从1开始，所以必须 传递 i+1
					NvrDiskInfo nvrDisk = nvrDiskDao.findByNvr(nvr.getIp(), i+1);
					if (nvrDisk == null){
						nvrDisk = new NvrDiskInfo();
						nvrDisk.setNvr(nvr);
						nvrDisk.setDiskIndex(i+1);
						nvrDisk.setCapacity(disk.getCapacity());
						nvrDisk.setFreeSpace(disk.getFreeSpace());
						nvrDisk.setPictureCapacity(disk.getPictureCapacity());
						nvrDisk.setFreePictureCapacity(disk.getFreePictureCapacity());
						nvrDiskDao.insert(nvrDisk);
					} else {
						nvrDisk.setCapacity(disk.getCapacity());
						nvrDisk.setFreeSpace(disk.getFreeSpace());
						nvrDisk.setPictureCapacity(disk.getPictureCapacity());
						nvrDisk.setFreePictureCapacity(disk.getFreePictureCapacity());
						nvrDiskDao.update(nvrDisk);
					}
					
					nvr.getDiskList().add(nvrDisk);
				}
			}
		}
		
		return nvrList;
	}

	@Override
	public List<NvrIpchannelInfo> getIpchannelOfNvr(String nvrIp) {
		// TODO Auto-generated method stub
		List<NvrIpchannelInfo> ipchannelList = ipchannelDao.findByNvr(nvrIp);
		
		return ipchannelList;
	}

	@Override
	public void scheduleScanNvrJob(String sn) {

		TriggerKey key = TriggerKey.triggerKey(sn + ":scan", sn);

		if (isTriggerExists(key)) {
			try {
				schedulerFacotry.getScheduler().pauseTrigger(key);
				schedulerFacotry.getScheduler().unscheduleJob(key);
			} catch (SchedulerException e) {
				LOG.error("unschedule exists trigger fail! triggerKey = " + key, e);
			}
		}

		// 获得nvr 会话信息失败时，
		// 需要启动 连接该nvr 的定时任务
		SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder
				.simpleSchedule().withIntervalInMinutes(SCHEDULE_INTERVER)
				.repeatForever();

		// job名例如 192.168.1.144:scan，
		// job组名 为 nvrIp, 例如 192.168.1.144
		JobDetail jobDetail = JobBuilder.newJob(NvrScanJob.class)
				.requestRecovery(false).withIdentity(sn + ":scan", sn)
				.usingJobData("sn", sn).build();

		Trigger trigger = TriggerBuilder
				.newTrigger()
				.withIdentity(sn + ":scan", sn)
				.forJob(jobDetail)
				.startAt(
						DateBuilder.futureDate(SCHEDULE_INTERVER,
								IntervalUnit.MINUTE))
				.withSchedule(schedBuilder).build();
		try {
			// 删除老的停止录像任务
			Trigger existTrigger =schedulerFacotry.getScheduler().getTrigger(trigger.getKey());
			if (existTrigger != null){
				schedulerFacotry.getScheduler().pauseTrigger(existTrigger.getKey());     
				schedulerFacotry.getScheduler().unscheduleJob(existTrigger.getKey()); 
			}
			
			// 若有重复的job, 覆盖该job
			schedulerFacotry.getScheduler().addJob(jobDetail, true, true);
			schedulerFacotry.getScheduler().scheduleJob(trigger);

			LOG.info("schedule nvr scan job! nvr sn = " + sn);
		} catch (SchedulerException e) {
			LOG.error("nvr scan job fail!", e);
		}

	}

	@Override
	@Transactional(value="nvmpJpaEM", propagation=Propagation.REQUIRED)
	public boolean scanNvr(String sn) {
		
		LOG.info("scan nvr is beginning---------------");
		LOG.info("---sn 	  : " + sn + "---");
		
		NvrDeviceInfo nvr = dvrDeviceDao.getNvrBySN(sn);
		String nvrIp = nvr.getIp();
		
		LOG.info("---nvrIp 	  : " + nvrIp + "---");
		
		
		String ip = nvr.getIp();
		int port = nvr.getPort();
		String userName =nvr.getUsername();
		String password = nvr.getPasswd();
		int type = nvr.getDeviceType();
		int subType = nvr.getSubDeviceType();
		int session = dvrCtrlInstance.NVRCreateSession(type, 
				subType, ip, port, userName, password);
		if (session == -1){
			// 表示nvr建立会话失败，不在线
			LOG.warn("nvr is not online!");
			nvr.setIsOnline(false);
			dvrSessions.put(ip, session);
		} else {
			// 更新nvr状态，在线
			nvr.setIsOnline(true);
			
			// 将上一次的nvr会话句柄释放掉
			int lastSession = dvrSessions.get(ip);
			if (lastSession != -1){
				dvrCtrlInstance.NVRDeleteSession(lastSession);
			}
			dvrSessions.put(ip, session);
		}
		
		dvrDeviceDao.update(nvr);
		
		LOG.info("scan nvr is end---------------");
		LOG.info( nvrIp + " is online? " + nvr.isOnline());
		
		// session == -1 表示nvr建立会话失败
		// 返回结果表示该nvr是否在线
		return session != -1;
	}

	@Override
	public Date getCurrentNvrTime(String nvrIp) {
		
		int session = dvrSessions.get(nvrIp);
		
		// 获得nvr系统时间
		TimeInfo timeInfo = new TimeInfo();
		int curTime = dvrCtrlInstance.NVRGetCurrentTime(session, timeInfo);
		if (curTime == 1){
			Date currentDate = NvrBeanTransUtil.transTimeInfoToDate(timeInfo);
			return currentDate;
		}else{
			// 获取NVR当前时间失败时，使用本地时间
			return new Date();
		}
		
	}

	@Override
	public PlanRecordInfo startPlanRecord(PlanRecordInfo record) {

		String ip = record.getIpChannel().getNvrDevice().getIp();
		Integer session = dvrSessions.get(ip);
		// 1 - 海康设备	0 - 保留子类型
//		int session = dvrCtrlInstance.DVRCreateSession(1, 0, ip, 8000, "admin", "12345");
		if (session == -1){
			LOG.warn("nvr is not online!");
			return null;
		}

		List<IPChannelInfo> listIPChannelInfo = new ArrayList<IPChannelInfo>();
		// IPChannelInfo 是 和 nvr.dll jni调用时的接口参数
		IPChannelInfo ipchannel = transRecordChannelToNvrIpchannel(record.getVideoDeviceChannel());
		listIPChannelInfo.add(ipchannel);

		// ctrlCode 1 - 添加ipchannel绑定
		int ipchannelBindResult = dvrCtrlInstance.NVRIPChannelCtrl(session,
					1, listIPChannelInfo, ipchannel.getNvrChannel());
		if (ipchannelBindResult != 1){
			LOG.warn("call nvr.dll to bind ipchannel fail!");
			return null;
		}
		// 根据返回的被绑定ip通道号，找到数据库中对应的DvrIpchannelInfo,并设置参数
		NvrIpchannelInfo dbIpchannel = ipchannelDao.findByNvrChannel(ip, ipchannel.getNvrChannel());
		record.getIpChannel().setDvrChannel(ipchannel.getNvrChannel());
		record.getIpChannel().setIsOnline(true);
		NvrBeanTransUtil.cloneIpchannelInfo(record.getIpChannel(), dbIpchannel);
		record.setIpChannel(dbIpchannel);
		
		// 绑定ip通道后，需要先检测ip通道录像设备，是否在线
		// 只有在线状态，才能开始录像
		int count = 10;
		while (count > 0){
			
			if (isOnline(ip, ipchannel.getNvrChannel()))
				break;
		
			// 若当前不在线，则休眠100ms,再去检测
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				LOG.warn("ipchannel online check is interruptted!");
				LOG.warn("nvrIp = " + ip + " / ipchannel = " + ipchannel.getNvrChannel());
			}
			count --;
		}
		
		// 总共有7天，一个星期的模板
		// 若dayTemplate 的isWholeDayMode为 false,并且schedTimeList中没有有效值，
		// 则 设置 param为 mode = 0, schedTimeX全部为0，表示 实际上 不录像
		for (DayTemplateInfo dayTemplate : record.getWeekTemplate().getWeekTemplate()) {

			PlanRecordParam param = NvrBeanTransUtil
					.getPlanRecordParamFromDayTemplate(dayTemplate);
			// 设置高级参数，是否录制音频数据
			param.setAudioRecord(record.isAudioRecord() ? 1 : 0);

			// 设置高级参数，是否录制存档
			param.setBackupRecord(record.isArchive() ? 1 : 0);

			// 设置高级参数，是否冗余备份
			param.setCopyRecord(record.isRedundancy() ? 1 : 0);

			// 设置高级参数，录像保存多少天
			param.setRecorderDuration(record.getRecordDuration());

			// 设置高级参数，录像锁定小时数
			param.setLockDuration(record.getLockDuration());
			
			param.setStreamType(record.getStreamType());
			param.setPreRecordTime(record.getPreRecordTime());
			param.setPostRecordTime(record.getPostRecordTime());
			
			int recordResult = dvrCtrlInstance.NVRRecordCtrl(session, 1, 1,
					ipchannel.getNvrChannel(), dayTemplate.getWeekIndex(), 0,
					param);
			if (recordResult != 1) {
				LOG.warn("call nvr.dll to start plan record fail!");
				// TO-DO
				// 增加定时任务 去开启计划录像
			}
		}
		
		// 计划录像后，立刻获得nvr系统时间，并记为录像开始时间
		TimeInfo timeInfo = new TimeInfo();
		int curTime = dvrCtrlInstance.NVRGetCurrentTime(session, timeInfo);
		if (curTime == 1){
			Date startDate = NvrBeanTransUtil.transTimeInfoToDate(timeInfo);
			record.setStartTime(startDate);
		}else{
			// 获取NVR当前时间失败时，使用本地时间
			record.setStartTime(new Date());
		}

		LOG.info("start manualRecord success!---------------");

		return record;
	}

	@Override
	public boolean stopPlanRecord(PlanRecordInfo recording) {
		
		LOG.info("stop plan record is beginning---------------");
		LOG.info("---deviceId 	  : " + recording.getDeviceId() + "---");
		LOG.info("---channelIndex : " + recording.getDeviceChannel() + "---");
		
		String nvrIp = recording.getIpChannel().getNvrDevice().getIp();
		Integer session = dvrSessions.get(nvrIp);
		if (session == -1){
			LOG.warn("nvr is not online! nvrIp = " + nvrIp);
			return false;
		}
		
		TimeInfo timeInfo = new TimeInfo();
		int curTime = dvrCtrlInstance.NVRGetCurrentTime(session, timeInfo);
		
		int ipchannelIndex = recording.getIpChannel().getNvrChannel();
		// recordMode-1 计划录像
		// ctrlCode-2 停止录像
		int stopResult = dvrCtrlInstance.NVRRecordCtrl(session, 1, 2, ipchannelIndex, 1, 0, null);
		if (stopResult == 0){
			LOG.warn("call nvr.dll to stop plan record fail!");
			return false;
		}

		if (curTime == 1){
			Date stopDate = NvrBeanTransUtil.transTimeInfoToDate(timeInfo);
			recording.setStopTime(stopDate);
		}else{
			// 获取NVR当前时间失败时，使用本地时间
			recording.setStopTime(new Date());
		}
		recording.setIsRecoding(false);
		
		// 解除被绑定的NVR ip通道
		// ctrlCode-2 删除绑定的nvr ip通道
		int ipchannelBindResult = dvrCtrlInstance.NVRIPChannelCtrl(session, 2, null, ipchannelIndex);
		if (ipchannelBindResult == 1){
			recording.getIpChannel().setIsIdle(true);
		} else {
			LOG.warn("call nvr.dll to unbind ipchannel fail!");
			recording.getIpChannel().setIsIdle(false);
			// To-Do 
			// 启动定时任务去解除绑定
			scheduleUnbindIpchannelJob(recording.getIpChannel().getNvrDevice().getDeviceId(), ipchannelIndex);
		}
		
		LOG.info("stop plan record success!---------------");
		
		return true;
	
	}

	@Override
	public List<FindResult> searchRecordFileByDevice(String nvrIp, FindCondition findCondition) {
		
		List<FindResult> result = new ArrayList<FindResult>();
		
		int session = dvrSessions.get(nvrIp);
		// session = -1, nvr不在线
		if (session == -1){
			LOG.warn("nvr is not online! nvrIp = " + nvrIp);
			return null;
		}
		
		int findResult = dvrCtrlInstance.NVRFindRecordFile(session, findCondition, result);
		if (findResult == 0){
			LOG.warn("find record file fail on nvr!");
			LOG.warn("nvrIp = " + nvrIp + " / channel = " + findCondition.getChannel());
			return null;
		}
		
		return result;
	}

	@Override
	public NvrDeviceInfo getNvrDevice(String nvrIp) {
		
		if (StringUtils.isEmpty(nvrIp))
			return null;
		
		NvrDeviceInfo nvr = dvrDeviceDao.getNvrByIp(nvrIp);
		
		return nvr;
	}

	@Override
	@Transactional(propagation=Propagation.REQUIRED, value="nvmpJpaEM")
	public void updateNvr(NvrUpdateBean nvr) {
		
		LOG.info("update nvr is beginning---------------");
		
		if (nvr == null || StringUtils.isEmpty(nvr.getSn())){
			LOG.warn("nvr is null or SN is null");
			return;
		}
		
		NvrDeviceInfo nvrDevice = dvrDeviceDao.getNvrBySN(nvr.getSn());
		
		// 根据sn号，没找到该nvr,直接返回
		if (nvrDevice == null){
			LOG.warn("can't find nvr by SN: " + nvr.getSn());
			return;
		}
		
		nvrDevice.setIp(nvr.getIp());
		nvrDevice.setUsername(nvr.getUsername());
		nvrDevice.setPasswd(nvr.getPasswd());
		nvrDevice.setPort(nvr.getPort());
		
		LOG.info("update nvr is success!---------------");
	}

	@Override
	@Transactional(propagation=Propagation.REQUIRED, value="nvmpJpaEM")
	public void deleteNvr(String sn) {
		
		LOG.info("delete nvr is beginning---------------");
		LOG.info("SN = " + sn);
		
		dvrDeviceDao.remove(dvrDeviceDao.getNvrBySN(sn));
		
		//  清除该NVR相关的所有Quartz job
		try {
			schedulerFacotry.getScheduler().pauseTriggers(GroupMatcher.triggerGroupEquals(sn));
			
			Set<TriggerKey> triggerKeys = schedulerFacotry.
					getScheduler().getTriggerKeys(GroupMatcher.triggerGroupEquals(sn));
			schedulerFacotry.getScheduler().unscheduleJobs(new ArrayList<TriggerKey>(triggerKeys));
		} catch (SchedulerException e) {
			LOG.error("delete nvr's quartz jobs fail", e);;
		}
		
		LOG.info("delete nvr is success!---------------");
	}

	@Override
	public VideoManualRecord startManualRecord(ManualRecordBean recordBean,
					String nvrIp, Integer ipchannel) {
		LOG.info("start manualRecord is beginning---------------");
		LOG.info("---nvrIp 	  : " + nvrIp + "---");
		LOG.info("---ipchannel : " + ipchannel + "---");
		
		VideoManualRecord record = transRecordBeanToDbRecord(recordBean);
		record.setIpChannel(ipchannelDao.findByNvrChannel(nvrIp, ipchannel));
		
		Integer session = dvrSessions.get(nvrIp);
		// 1 - 海康设备	0 - 保留子类型
//		int session = dvrCtrlInstance.DVRCreateSession(1, 0, ip, 8000, "admin", "12345");
		if (session == -1){
			LOG.warn("nvr is not online!");
			return null;
		}

		PlanRecordParam param = new PlanRecordParam();
		int recordResult = dvrCtrlInstance.NVRRecordCtrl(session, 2, 1, ipchannel, 0, 0, param);
		if (recordResult != 1){
			LOG.warn("call nvr.dll to start manual record fail!");
			return null;
		}
		
		// 手工录像后，立刻获得nvr系统时间，并记为录像开始时间
		TimeInfo timeInfo = new TimeInfo();
		int curTime = dvrCtrlInstance.NVRGetCurrentTime(session, timeInfo);
		if (curTime == 1){
			Date startDate = NvrBeanTransUtil.transTimeInfoToDate(timeInfo);
			record.setStartTime(startDate);
		}else{
			// 获取NVR当前时间失败时，使用本地时间
			record.setStartTime(new Date());
		}

		LOG.info("start manualRecord success!---------------");
		LOG.info("record at nvr = " + nvrIp + " / ipchannel = " + ipchannel);
		
		return record;
	}

	@Override
	@Transactional(propagation=Propagation.REQUIRED, value="nvmpJpaEM")
	public boolean stopManualRecord(String sn, int channel,
			boolean isUnbindIpchannel) {
		
		LOG.info("stop manualRecord job is beginning---------------");
		LOG.info("---nvr sn 	  : " + sn + "---");
		LOG.info("---channel : " + channel + "---");
		
		NvrDeviceInfo nvr = dvrDeviceDao.getNvrBySN(sn);
		if (nvr == null)
			return false;
		
		String nvrIp = nvr.getIp();
		Integer session = dvrSessions.get(nvrIp);
		if (session == -1){
			return false;
		}
		
		// recordMode-2 手工录像
		// ctrlCode-2 停止录像
		int stopResult = dvrCtrlInstance.NVRRecordCtrl(session, 2, 2, channel, 0, 0, null);
		if (stopResult == 0){
			LOG.warn("call nvr.dll to stop manual record job fail!");
			return false;
		} else {
			manualRecordService.stopRecordingOfIpchannel(nvrIp, channel);
		}
		
		if (isUnbindIpchannel){

			NvrIpchannelInfo ipchannelInfo = ipchannelDao.findByNvrChannel(nvrIp, channel);
			// 解除被绑定的NVR ip通道
			// ctrlCode-2 删除绑定的nvr ip通道
			int ipchannelBindResult = dvrCtrlInstance.NVRIPChannelCtrl(session, 2, null, ipchannelInfo.getNvrChannel());
			if (ipchannelBindResult == 1){
				ipchannelInfo.setIsIdle(true);
			} else {
				LOG.warn("call nvr.dll to unbind ipchannel fail!");
				ipchannelInfo.setIsIdle(false);
				// To-Do 
				// 启动定时任务去解除绑定
				scheduleUnbindIpchannelJob(nvr.getDeviceId(), channel);
			}
		}
		
		LOG.info("stop manualRecord job success!---------------");
		
		return true;
	}

	@Override
	public boolean updatePlanRecord(String sn, int ipchannel,
			PlanRecordParamBean updateParam, boolean needSchedule) {

		NvrDeviceInfo nvr = dvrDeviceDao.getNvrBySN(sn);
		String nvrIp = nvr.getIp();
		
		Integer session = dvrSessions.get(nvrIp);
		// 1 - 海康设备	0 - 保留子类型
//		int session = dvrCtrlInstance.DVRCreateSession(1, 0, ip, 8000, "admin", "12345");
		if (session == -1){
			LOG.warn("nvr is not online!");
			return false;
		}
		
		WeekTemplateInfo weekTemplate = weekTemplateDao.getTemplateByName(updateParam.getTemplateName());

		// 总共有7天，一个星期的模板
		// 若dayTemplate 的isWholeDayMode为 false,并且schedTimeList中没有有效值，
		// 则 设置 param为 mode = 0, schedTimeX全部为0，表示 实际上 不录像
		for (DayTemplateInfo dayTemplate : weekTemplate.getWeekTemplate()) {

			PlanRecordParam param = NvrBeanTransUtil
					.getPlanRecordParamFromDayTemplate(dayTemplate);
			// 设置高级参数，是否录制音频数据
			param.setAudioRecord(updateParam.isAudioRecord() ? 1 : 0);

			// 设置高级参数，是否录制存档
			param.setBackupRecord(updateParam.isArchive() ? 1 : 0);

			// 设置高级参数，是否冗余备份
			param.setCopyRecord(updateParam.isRedundancy() ? 1 : 0);

			// 设置高级参数，录像保存多少天
			param.setRecorderDuration(updateParam.getRecordDuration());

			// 设置高级参数，录像锁定小时数
			param.setLockDuration(updateParam.getLockDuration());
			
			param.setStreamType(updateParam.getStreamType());
			param.setPreRecordTime(updateParam.getPreRecordTime());
			param.setPostRecordTime(updateParam.getPostRecordTime());

			int recordResult = dvrCtrlInstance.NVRRecordCtrl(session, 1, 1,
					ipchannel, dayTemplate.getWeekIndex(), 0, param);
//			int recordResult = 0;
			if (recordResult != 1) {
				LOG.warn("call nvr.dll to start plan record fail!");
				// TO-DO
				// 增加定时任务 去开启计划录像
				if (needSchedule){
					schedulePlanRecordJob(sn, ipchannel, updateParam);
				}
				return false;
			}
		}
		
		LOG.info("update plan record success!---------------");

		return true;
	}

	@Override
	public void schedulePlanRecordJob(String sn, int channel,
			PlanRecordParamBean param) {

		// 获得nvr信息成功，但是获取ip通道绑定信息失败时，
		// 需要启动 获取并初始化该nvr ip通道的定时任务
		SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder
				.simpleSchedule().withIntervalInMinutes(SCHEDULE_INTERVER).withRepeatCount(MAX_REPEAT);//.repeatForever();

		
		Map<String, Object> data = new HashMap<String, Object>();
		data.put("startParam", param);
		JobDataMap jobDataMap = JobDataMapSupport.newJobDataMap(data);
		// job名例如  XT1620140829BBRR478206093WCVU:2:stop，
		// job组名 为 nvr sn, 例如 XT1620140829BBRR478206093WCVU
		JobDetail jobDetail = JobBuilder.newJob(PlanRecordStartJob.class)
					.requestRecovery(true).withIdentity(sn + ":" + channel + ":plan",sn)
					.usingJobData("sn", sn).usingJobData("channel", channel)
					.usingJobData(jobDataMap).build();
		
		Trigger trigger = TriggerBuilder.newTrigger()
				.withIdentity(sn + ":" + channel + ":plan",sn).forJob(jobDetail)
				.startAt(DateBuilder.futureDate(SCHEDULE_INTERVER, IntervalUnit.MINUTE))
				.withSchedule(schedBuilder).build();
		try {
			// 删除老的停止录像任务
			Trigger existTrigger =schedulerFacotry.getScheduler().getTrigger(trigger.getKey());
			if (existTrigger != null){
				schedulerFacotry.getScheduler().pauseTrigger(existTrigger.getKey());     
				schedulerFacotry.getScheduler().unscheduleJob(existTrigger.getKey()); 
			}
			
			// 若有重复的job, 覆盖该job
			schedulerFacotry.getScheduler().addJob(jobDetail, true, true);
			schedulerFacotry.getScheduler().scheduleJob(trigger);
			
			LOG.info("schedule plan record start job! nvrIp = " +
					sn + " / channel = " + channel);
		} catch (SchedulerException e) {
			LOG.error("schedule plan record start job fail!", e);
		}
	}

	@Override
	public boolean formatDisk(String sn, int diskIndex) {
		
		LOG.info("format nvr disk is beginning---------------");
		LOG.info("---sn 	   : " + sn + "---");
		LOG.info("---diskIndex : " + diskIndex + "---");

		NvrDeviceInfo nvr = dvrDeviceDao.getNvrBySN(sn);
		String nvrIp = nvr.getIp();
		
		Integer session = dvrSessions.get(nvrIp);
		if (session == -1){
			LOG.warn("nvr is not online!");
			return false;
		}
		
		// ctrlCode = 1,表示格式化磁盘
		int formatResult = dvrCtrlInstance.NVRDiskCtrl(session, diskIndex, 1, null);
		if (formatResult != 1){
			LOG.warn("call nvr to format nvr disk fail!");
			return false;
		}
		
		LOG.info("format nvr disk is success!---------------");
		return true;
	}

}
